package org.peterbjornx.net;

import org.peterbjornx.io.*;
import org.peterbjornx.util.TextUtils;
import org.peterbjornx.config.ProtocolSettings;
import org.peterbjornx.Client;

import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;

/**
 * Created by IntelliJ IDEA.
 * User: PeterbjornX
 * Date: Apr 8, 2009
 * Time: 6:03:28 PM
 * Initiates the game connection and authenticates the player
 */
public class LoginHandler {
    private boolean reconnect;
    private RSOutputStream outstream;
    private String username;
    private long namehash;

    private RSInputStream instream;
    private static final int LOGIN_HANDSHAKE = 0;
    private int status;
    private static final int LOGIN_DONE = 3;
    private static final int LOGIN_STATUS = 1;
    private int result;
    private static final int LOGIN_VARBLOCK = 2;
    private static final int RESULT_EXCHANGE_KEYS = 0;
    private long serverkey;
    private int[] isaacseed;
    private static final int RSA_DECODE_CHECK = 10;
    private static final int CONNECTION_GAME = 14;
    private int uid;
    private String password;
    private static final int CONNECTION_RECONNECT = 18;
    private static final int CONNECTION_LOGIN = 16;
    private int[] crcs;
    private static final int RESULT_RELOGIN = 1;
    private static final int LOGIN_INIT = -1;
    private static final int RESULT_OK = 2;
    private int privileges;
    private boolean debugging;
    private static final int LOGIN_ERROR = 4;
    private Client clientinstance;
    private RSOutputStream realoutstream;
    private ByteArrayOutputStream byteout;

    public LoginHandler(Client client){
        clientinstance = client;
        crcs = new int[9];
    }

    private void flush() throws IOException {
        outstream.flush();
        byte d[] = byteout.toByteArray();
        realoutstream.write(d);
        byteout = new ByteArrayOutputStream();
        outstream.set(byteout);
    }

    public int[] getISAACSeed(){
        return isaacseed;
    }

    public long getServerkey(){
        return serverkey;
    }

    public int getPrivileges(){
        return privileges;
    }

    public boolean getDebugflag(){
        return debugging;
    }

    public int getResult(){
        return result;
    }

    public void setCRCs(int[] _crcs){
        crcs = _crcs;
    }

    public void setType(boolean _reconnect){
        reconnect = _reconnect;
    }

    public void setUID(int _uid){
        uid = _uid;
    }

    public void setUsername(String name){
        username = name;
        namehash = TextUtils.longForName(username);
    }

    public void setPassword(String pass){
        password = pass;
    }

    public void runLogin() throws IOException {
        status = LOGIN_INIT;
        while (status != LOGIN_DONE && status != LOGIN_ERROR){
            while (instream != null && instream.available() == 0){
                /*.*/
            }
            process();
        }
    }

    private void process() throws IOException {       //Sorry for the name
        switch(status){
            case LOGIN_INIT:
                if (!clientinstance.connectgameserver("127.0.0.1",43594))
                {
                    status = LOGIN_ERROR;
                    result = -1;
                    return;
                }
                outstream.writeByte(CONNECTION_GAME);
                outstream.writeByte((int)(namehash >> 16 & 31L));
                flush();
                status = LOGIN_HANDSHAKE;
                break;
            case LOGIN_HANDSHAKE:
                for (int bidx = 0;bidx < 8;bidx++){
                    //noinspection ResultOfMethodCallIgnored
                    instream.read();
                }
                status = LOGIN_STATUS;
                break;
            case LOGIN_STATUS:
                result = instream.read();
                status = LOGIN_VARBLOCK;
                break;
            case LOGIN_VARBLOCK:
                switch(result){
                    case RESULT_EXCHANGE_KEYS:
                        serverkey = instream.readLong();
                        isaacseed = new int[4];
                        isaacseed[0] = (int)(Math.random() * 99999999D);
                        isaacseed[1] = (int)(Math.random() * 99999999D);
                        isaacseed[2] = (int)(serverkey >> 32);
                        isaacseed[3] = (int)serverkey;
                        ByteArrayOutputStream b = new ByteArrayOutputStream();
                        RSOutputStream rsastream = new RSOutputStream(b);
                        //Start of RSA block, i do not implement RSA encryption as most servers dont use/support it
                        rsastream.writeByte(RSA_DECODE_CHECK);
                        rsastream.writeInt(isaacseed[0]);
                        rsastream.writeInt(isaacseed[1]);
                        rsastream.writeInt(isaacseed[2]);
                        rsastream.writeInt(isaacseed[3]);
                        rsastream.writeInt(uid);
                        rsastream.writeString10T(username);
                        rsastream.writeString10T(password);
                        int rsalen = rsastream.getWritten();
                        rsastream.flush();
                        b.flush();
                        //End of RSA block
                        if (reconnect)
                            outstream.write(CONNECTION_RECONNECT);
                        else
                            outstream.write(CONNECTION_LOGIN);
                        outstream.write(rsalen+40);//Total length
                        flush();
                        outstream.write(255);//Value is not byte
                        outstream.writeShort(ProtocolSettings.VERSION);//Send version
                        outstream.write(0);//Were not emulating a low mem client
                        for (int crcidx = 0;crcidx < 9;crcidx++)
                            outstream.writeInt(crcs[crcidx]);
                        outstream.write(rsalen-1);
                        outstream.write(b.toByteArray());
                        flush();
                        result = instream.read();
                        process();
                        break;
                    case RESULT_RELOGIN:
                        status = LOGIN_INIT;
                        break;
                    case RESULT_OK:
                        privileges = instream.read();
                        debugging = instream.read() == 1;
                        status = LOGIN_DONE;
                        break;
                    default:
                        System.err.println("WRONG RESPONSE:"+result);
                        status = LOGIN_ERROR;
                }
                break;
        }
    }

    public void setStreams(InputStream _instream, OutputStream _outstream) {
        byteout = new ByteArrayOutputStream();
        outstream = new RSOutputStream(byteout);
        instream = new RSInputStream(_instream,0);
        realoutstream = new RSOutputStream(_outstream);
    }
}
